/**
 * **********************************************************************
 * Request.java
 * Universidad del Valle de Guatemala
 * Algoritmos y Estructura de Datos
 * @autor Martín Guzmán, Karen Tojín, Jeffry Turcios, David Coronado
 * Fecha: 30 de noviembre de 2009
 * **********************************************************************
 */
/**
 * Descripción: clase que tiene la servidor del sistema
 */
import java.net.*;
import java.io.*;

import java.util.Calendar;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jdom.*;

public class Server {

    public static Cola_implementacion_JCF cola = new Cola_implementacion_JCF(10000);
    public static RandomAccessFile Access_File;
    public static LinkedList<Integer> hashes = new LinkedList<Integer>();
    public static LinkedList<Long> arbol = new LinkedList<Long>();
    public static LinkedList<Integer> hashes_apellido = new LinkedList<Integer>();
    public static LinkedList<Integer> hashes_carrera = new LinkedList<Integer>();
    public static String respuesta = "";
    public static Document doc;
    public static LinkedList<String> clientes = new LinkedList<String>();
    public static String Cliente_actual = "";
    public static Socket s1;
    public static String tiempo_entrada = "";
    public static int hora_entrada, minutos_entrada, segundos_entrada;
    public static String accion;

    /**
     * Método main para preparar al servidor del sistema
     * @param args
     */
    public static void main(String args[]) {
        ServerSocket s = null;
        //crea el archivo de DB
        try {
            Access_File = new RandomAccessFile(new File("DB.txt"), "rw");
            Recargar_hashes();
            //Access_File.setLength(0);
        } catch (Exception ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }

        try {
            s = new ServerSocket(5432);
        } catch (IOException e) {
            e.printStackTrace();
        }


        // ciclo infinito que espera a que se envie un request
        while (true) {

            try {
                System.out.println("\n\n--------------------------------------------------------\nEsperando un request de algún cliente...\n--------------------------------------------------------\n");
                //espera a que entre un request
                //pasa una vez se haya ingresado un request
                s1 = s.accept();

                //*************************************************************
                //Ver qué usuario entra
                //*************************************************************
                Calendar calendario = Calendar.getInstance();
                tiempo_entrada = "" + calendario.get(Calendar.HOUR_OF_DAY) + ":" + calendario.get(Calendar.MINUTE) + ":" + calendario.get(Calendar.SECOND) + ":" + calendario.get(Calendar.MILLISECOND);
                //System.out.println(tiempo_entrada);

                VerUsuarioEntrante();

                //obtiene lo que esté en el request
                ObjectInputStream dis = new ObjectInputStream(s1.getInputStream());
                doc = (Document) dis.readObject();

                Parsear();
                Thread_2 hilo = new Thread_2();
                hilo.start();

                /*
                //*************************************************************
                //Realizar los request que vengan
                //*************************************************************
                while(cola.empty()==false){
                Request request_temp = cola.pop();
                Realizar_Request(request_temp);
                }

                //*************************************************************
                //para responderle al cliente
                //*************************************************************
                ObjectOutputStream slout = new ObjectOutputStream(s1.getOutputStream());

                Guardar_log();
                //*************************************************************
                //Respuesta del servidor al cliente
                //*************************************************************
                //System.out.println(respuesta);
                slout.writeObject(respuesta);

                Guardar_hashes();
                 */

                //s1.close();

            } catch (Exception e) {
                System.out.println("");
                e.printStackTrace();
            }
        }
    }

    /**
     * Método que realiza el request enviado por el cliente
     * @param request_temp: la hora a la que fue enviado el request
     */
    public static void Realizar_Request(Request request_temp) {
        //verificar el tipo:
        int tipo = request_temp.getTipo();
        switch (tipo) {
            case 0:

                break;
            case 1:
                accion = "ingreso";
                Ingreso(request_temp.getCampos());
                break;
            case 2:
                accion = "modificación";
                Modificar(request_temp.getCampos());
                break;
            case 3:
                accion = "eliminado";
                Eliminar(request_temp.getCampos());
                break;
            case 4:
                accion = "búsqueda";
                Buscar(request_temp.getCampos());
                break;
            default:
                respuesta = "\n******************************************\nIngreso inválido. Operación no válida\n******************************************\n";
                System.out.println("Error con el request. Operación no válida");
                break;
        }
    }

    /**
     * Método que ingresa los registros a la base de datos
     * @param campos: los campos del ingreso
     */
    private static void Ingreso(LinkedList<String> campos) {
        int code = campos.get(0).hashCode();

        if (hashes.contains(code) == true) {
            respuesta = "\n******************************************\nIngreso inválido. Registro ya existente\n******************************************\n";
            System.out.println("El registro ya existe");
        } else {
            hashes.add(code);
            hashes_apellido.add(campos.get(2).hashCode());
            hashes_carrera.add(campos.get(3).hashCode());

            try {
                //System.out.println(""+Access_File.length());
                arbol.add(Access_File.length());
                Access_File.seek(Access_File.length());
                Access_File.writeBytes(campos.get(0) + "," + campos.get(1) + "," + campos.get(2) + "," + campos.get(3) + "\n");

            } catch (IOException ex) {
                Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
            }
            respuesta = "\n******************************************\nIngreso realizado correctamente.\n******************************************\n";
        }
    }

    /**
     * Método que modifica registros en la clase de datos
     * @param campos: los campos a modificar
     */
    private static void Modificar(LinkedList<String> campos) {
        int code = campos.get(0).hashCode();

        if (hashes.contains(code) == true) {
            int index = hashes.indexOf(code);
            Long original_sin_modificar = arbol.get(index);
            try {
                //apunta al original para sacarle "copia"
                Access_File.seek(original_sin_modificar);
                String[] campos_temp = Access_File.readLine().split(",");
                for (int i = 0; i < campos.size(); i++) {
                    if (campos.get(i) == null) {
                        campos.remove(i);
                        campos.add(i, campos_temp[i]);
                    }
                }
                hashes_apellido.remove(index);
                hashes_apellido.add(index, campos.get(2).hashCode());


                hashes_carrera.remove(index);
                hashes_carrera.add(index, campos.get(3).hashCode());

                arbol.remove(index);
                arbol.add(index, Access_File.length());
                //System.out.println(Access_File.length());
                Access_File.seek(Access_File.length());
                Access_File.writeBytes(campos.get(0) + "," + campos.get(1) + "," + campos.get(2) + "," + campos.get(3) + "\n");
            } catch (IOException ex) {
                Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
            }
            respuesta = "\n******************************************\nSe ha modificado correctamente.\n******************************************\n";
        } else {
            respuesta = "\n******************************************\nEl registro no existe. Modificación no hecha.\n******************************************\n";
            System.out.println("El registro no existe");
        }
    }

    /**
     * Método que elimina registros de la base de datos
     * @param campos: el registro a eliminar
     */
    private static void Eliminar(LinkedList<String> campos) {
        int code = campos.get(0).hashCode();

        if (hashes.contains(code) == true) {
            int index = hashes.indexOf(code);
            hashes.remove(index);
            hashes_apellido.remove(index);
            hashes_carrera.remove(index);
            arbol.remove(index);
            respuesta = "\n******************************************\nRegistro eliminado correctamente.\n******************************************\n";
        } else {
            respuesta = "\n******************************************\nEl registro no existe. Eliminación no hecha\n******************************************\n";
            System.out.println("El registro no existe y no se puede borrar");
        }
    }

    /**
     * Método que busca las ocurrecias de un campo dentro de la base de datos
     * @param campos: campo indexado a buscar
     */
    private static void Buscar(LinkedList<String> campos) {
        if (campos.get(0) != null) {
            //búsqueda por carné
            int code = campos.get(0).hashCode();
            int index = hashes.indexOf(code);
            Long original_sin_modificar = arbol.get(index);
            respuesta = "\n********* Resultados de la búsqueda *********\n";
            try {
                //apunta al original para sacarle "copia"
                Access_File.seek(original_sin_modificar);
                respuesta += Access_File.readLine().replaceAll(",", " ");

                Access_File.seek(Access_File.length());
            } catch (IOException ex) {
                Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
            }

        } else if (campos.get(1) != null) {
            //búsqueda por nombre
            respuesta = "\n****************** Resultados de la búsqueda ******************\n";
            respuesta += "No se puede buscar por nombre ya que no se encuentra indexada";
        } else if (campos.get(2) != null) {
            //búsqueda por apellido
            Buscar_por_apellido(campos);
        } else if (campos.get(3) != null) {
            //búsqueda por carrera
            Buscar_por_carrera(campos);
        }
        /*
        for(int i=0;i<campos.size();i++){
        System.out.println(campos.get(i));
        }
         */
    }

    /**
     * Método que tiene el parser, inserta en la cola el doc XML enviado
     */
    private static void Parsear() {
        //**************************************************************
        // parsear el Docuement recibido, ya que este representa un XML
        //**************************************************************
        XML_Parser tried = new XML_Parser();
        cola.push(tried.parser(doc));
        //System.out.println(tried.parser(doc));
        //**************************************************************
    }

    /**
     * Método para ver e identificar al cliente enviando el request
     */
    private static void VerUsuarioEntrante() {
        if (clientes.contains(s1.getInetAddress().toString()) == false) {
            clientes.add(s1.getInetAddress().toString());
            Cliente_actual = "Cliente " + clientes.indexOf(s1.getInetAddress().toString()) + " con IP: " + s1.getInetAddress().toString();
        } else {
            Cliente_actual = "Cliente " + clientes.indexOf(s1.getInetAddress().toString()) + " con IP: " + s1.getInetAddress().toString();
        }

        System.out.println("Ha ingresado una petición del " + Cliente_actual);
    }

    /**
     * Método para recargar los datos previamente ingresados en la base de datos
     * @throws FileNotFoundException
     * @throws IOException
     * @throws Exception
     */
    private static void Recargar_hashes() throws FileNotFoundException, IOException, Exception {

        hashes.clear();
        hashes_apellido.clear();
        hashes_carrera.clear();
        arbol.clear();

        BufferedReader bf = new BufferedReader(new FileReader(new File("hashes.dat")));
        String linea = bf.readLine();
        while (linea != null) {
            hashes.add(Integer.parseInt(linea));
            linea = bf.readLine();
        }

        bf = new BufferedReader(new FileReader(new File("indices.dat")));
        linea = bf.readLine();
        while (linea != null) {
            arbol.add(Long.parseLong(linea));
            linea = bf.readLine();
        }

        bf = new BufferedReader(new FileReader(new File("hashes_apellido.dat")));
        linea = bf.readLine();
        while (linea != null) {
            hashes_apellido.add(Integer.parseInt(linea));
            linea = bf.readLine();
        }

        bf = new BufferedReader(new FileReader(new File("hashes_carrera.dat")));
        linea = bf.readLine();
        while (linea != null) {
            hashes_carrera.add(Integer.parseInt(linea));
            linea = bf.readLine();
        }



    }

    /**
     * Método que guarda los hashes generados
     * @throws IOException
     */
    public static void Guardar_hashes() throws IOException {

        String file = "hashes.dat";
        BufferedWriter bw = new BufferedWriter(new FileWriter(file));
        PrintWriter outFile = new PrintWriter(bw);

        for (int i = 0; i < hashes.size(); i++) {
            outFile.print(hashes.get(i) + "\n");
        }
        outFile.close();

        file = "indices.dat";
        bw = new BufferedWriter(new FileWriter(file));
        outFile = new PrintWriter(bw);

        for (int i = 0; i < arbol.size(); i++) {
            outFile.print(arbol.get(i) + "\n");
        }
        outFile.close();

        file = "hashes_apellido.dat";
        bw = new BufferedWriter(new FileWriter(file));
        outFile = new PrintWriter(bw);

        for (int i = 0; i < hashes_apellido.size(); i++) {
            //System.out.println(hashes_apellido.get(i));
            outFile.print(hashes_apellido.get(i) + "\n");
        }
        outFile.close();

        file = "hashes_carrera.dat";
        bw = new BufferedWriter(new FileWriter(file));
        outFile = new PrintWriter(bw);

        for (int i = 0; i < hashes_carrera.size(); i++) {
            //System.out.println(hashes_apellido.get(i));
            outFile.print(hashes_carrera.get(i) + "\n");
        }
        outFile.close();

    }

    /**
     * Método para buscar por apellidos dentro de la base de datos
     * @param campos a buscar
     */
    private static void Buscar_por_apellido(LinkedList<String> campos) {


        int code = campos.get(2).hashCode();

        respuesta = "\n********* Resultados de la búsqueda *********\n";

        for (int i = 0; i < hashes_apellido.size(); i++) {
            if (hashes_apellido.get(i).compareTo(code) == 0) {
                //adjuntar lo que esté en el índice en la posición i del archivo
                Long original_sin_modificar = arbol.get(i);

                try {
                    Access_File.seek(original_sin_modificar);
                    respuesta += Access_File.readLine().replaceAll(",", " ") + "\n";
                } catch (IOException ex) {
                    Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }

        try {
            //apunta al original para sacarle "copia"
            Access_File.seek(Access_File.length());
        } catch (IOException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Método para buscar por carrera dentro de la base de datos
     * @param campos a buscar
     */
    private static void Buscar_por_carrera(LinkedList<String> campos) {
        int code = campos.get(3).hashCode();

        respuesta = "\n********* Resultados de la búsqueda *********\n";

        for (int i = 0; i < hashes_carrera.size(); i++) {
            if (hashes_carrera.get(i).compareTo(code) == 0) {
                //adjuntar lo que esté en el índice en la posición i del archivo
                Long original_sin_modificar = arbol.get(i);

                try {
                    Access_File.seek(original_sin_modificar);
                    respuesta += Access_File.readLine().replaceAll(",", " ") + "\n";
                } catch (IOException ex) {
                    Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }

        try {
            //apunta al original para sacarle "copia"
            Access_File.seek(Access_File.length());
        } catch (IOException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Método para guardar en un archivo el log del servidor
     * @throws IOException
     */
    public static void Guardar_log() throws IOException {
        String file = "log.dat";
        BufferedWriter bw = new BufferedWriter(new FileWriter(file, true));
        PrintWriter outFile = new PrintWriter(bw);
        String Log = "";

        //obtener el tiempo en el que se le contesta al cliente
        Calendar calendario = Calendar.getInstance();
        String tiempo_salida = "" + calendario.get(Calendar.HOUR_OF_DAY) + ":" + calendario.get(Calendar.MINUTE) + ":" + calendario.get(Calendar.SECOND) + ":" + calendario.get(Calendar.MILLISECOND);
        Log += Cliente_actual + " hizo un request de " + accion + ". Hora de entrada de request: " + tiempo_entrada + " Hora de salida de request: " + tiempo_salida;
        outFile.print(Log + "\n");

        outFile.close();
    }
}

